package org.massdeception.content.util;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Collections;
import javax.cache.Cache;
import javax.cache.CacheManager;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import org.massdeception.content.entities.MOTD;
import org.massdeception.content.entities.PMF;
import org.massdeception.content.entities.Slide;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

public class SlideUtils
{
    private static final Logger log = Logger.getLogger(SlideUtils.class.getName());
    
    private static Cache cache;
    
    private static SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd");
    
    private static final String UI_DATE_FORMAT = "MM/dd/yyyy";

    private static SimpleDateFormat formatterUI = new SimpleDateFormat(UI_DATE_FORMAT);
    
    public static final TimeZone TIMEZONE = TimeZone.getTimeZone("America/Los_Angeles");
    
    static
    {
        try 
        {
            cache = CacheManager.getInstance().getCacheFactory().createCache(Collections.emptyMap());
        } 
        catch (Throwable t) 
        {
            log.log(Level.SEVERE, "Could not get an instance of the cache", t);
        }
    }
    
    public static String getUIDate(Long normDate)
    {
        try
        {
            return(formatterUI.format(formatter.parse(normDate.toString())));
        }
        catch(Throwable t)
        {
            log.log(Level.SEVERE, "Could not convert long to ui date", t);
            return("");
        }        
    }
    
    public static Long getNormalizedUIDate(String uiDate, boolean applyOffset)
    {
        try
        {
            Date orig = formatterUI.parse(uiDate);
            
            if ( applyOffset )
            {
                Calendar cal = Calendar.getInstance();
                cal.setTime(orig);
                int offset = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / (60 * 1000);
                cal.add(Calendar.MINUTE, offset);
                
                orig = cal.getTime();
            }
            
            return(Long.parseLong(formatter.format(orig)));
        }
        catch (Throwable t)
        {
            log.log(Level.WARNING, "Could not parse UI date string.", t);
            return(null);
        }
    }
    
    public static Long getNormalizedDate(String date, boolean applyOffset)
    {
        try
        {
            return(getNormalizedDate(formatter.parse(date), applyOffset));
        }
        catch (Throwable t)
        {
            log.log(Level.WARNING, "Could not parse date string.", t);
            return(null);
        }
    }
    
    public static Long getNormalizedDate(Date date, boolean applyOffset)
    {
        //log.log(Level.WARNING, "inc date: "+date);
        try
        {
            if ( applyOffset )
            {
                Calendar cal = Calendar.getInstance();
                cal.setTimeZone(TIMEZONE);
                cal.setTime(date);
                int offset = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / (60 * 1000);
                cal.add(Calendar.MINUTE, offset);

                date = cal.getTime();
                
                //String formattedDate = formatter.format(newDate);
                //log.log(Level.WARNING, "offset date: "+date);
                
                //return(Long.parseLong(formattedDate));
            }
            //log.log(Level.WARNING, "non-offset date: "+date);
            
            return(Long.parseLong(formatter.format((date))));
        }
        catch (Throwable t)
        {
            log.log(Level.WARNING, "Could not parse date string.", t);
            return(null);
        }
    }
    
    /*
    public static Date getParsedDate(String date)
    {
        try
        {
            Date beginning = null;
            
            if ( "today".equals(date))
                beginning = new Date();
            else 
                beginning = formatter.parse(date);
            
            Calendar cal = Calendar.getInstance();
            cal.setTimeZone(TimeZone.getTimeZone(TZ));
            
            int offset = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / (60 * 1000);
            
            //log.log(Level.WARNING, "offset: "+offset);
            
            cal.setTime(beginning);
            cal.add(Calendar.MINUTE, offset);
            
            return(cal.getTime());
        }
        catch (Throwable t)
        {
            return(null);
        }
    }

    public static Date getBeginningOfDay(Date date)
    {
        Calendar cal = Calendar.getInstance();
        cal.setTimeZone(TimeZone.getTimeZone(TZ));
        
        cal.setTime(date);
        cal.set(Calendar.HOUR_OF_DAY, 0); 
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, -1);
            
        return(cal.getTime());
    }
    
    public static Date getTomorrow(Date date)
    {
        Calendar cal = Calendar.getInstance();
        cal.setTimeZone(TimeZone.getTimeZone(TZ));
            
        cal.setTime(date);
        cal.set(Calendar.HOUR_OF_DAY, 0); 
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.add(Calendar.DATE, 1);
        
        return(cal.getTime());
    }
    
    public static Slide getSlideForTomorrow(Date date)
    {
        Calendar cal = Calendar.getInstance();
        cal.setTimeZone(TimeZone.getTimeZone(TZ));
        
        cal.setTime(date);
        cal.add(Calendar.DATE, 1);
        
        return(getSlideByDay(cal.getTime()));
    }
    */
    
    public static Slide getPreviousSlide(Long currentSlideDay)
    {
        PersistenceManager pm = PMF.get().getPersistenceManager();
        Slide slide = null;
        try
        {
            slide = getSlideFromCache(currentSlideDay-1);
            if ( slide != null )
                return(slide);
            
            Query query = pm.newQuery(Slide.class);
            query.setFilter("publishDate < publishDateParam");
            query.setRange(0, 1);
            query.setOrdering("publishDate desc");
            query.declareParameters("Long publishDateParam");
            List<Slide> results = (List<Slide>) query.execute(currentSlideDay);
            
            if ( results.size() > 0 )
            {
                slide = (Slide) results.get(0);
                
                //log.log(Level.WARNING, "##### slide a: "+currentSlideDay);
                //log.log(Level.WARNING, "##### slide: "+slide.getPublishDate());
                
                setSlideInCache(slide);
                return(slide);
            }
            else
                return(null);
        }
        catch (Throwable t)
        {
            log.log(Level.WARNING, "Could not query for slide by day "+currentSlideDay, t);
            return(null);
        }
        finally
        {
            pm.close();
        }
    }
    
    public static Slide getNextSlide(Long currentSlideDay)
    {
        PersistenceManager pm = PMF.get().getPersistenceManager();
        Slide slide = null;
        try
        {
            slide = getSlideFromCache(currentSlideDay+1);
            if ( slide != null )
                return(slide);
            
            Query query = pm.newQuery(Slide.class);
            query.setFilter("publishDate > publishDateParam");
            query.setRange(0, 1);
            query.setOrdering("publishDate asc");
            query.declareParameters("Long publishDateParam");
            List<Slide> results = (List<Slide>) query.execute(currentSlideDay);
            
            if ( results.size() > 0 )
            {
                slide = (Slide) results.get(0);
                setSlideInCache(slide);
                return(slide);
            }
            else
                return(null);
        }
        catch (Throwable t)
        {
            log.log(Level.WARNING, "Could not query for slide by day "+currentSlideDay, t);
            return(null);
        }
        finally
        {
            pm.close();
        }
    }
    
    public static Slide getSlideByDay(Long normDate)
    {
        PersistenceManager pm = PMF.get().getPersistenceManager();
        try
        {
            Slide slide = getSlideFromCache(normDate);
            if ( slide != null )
                return(slide);
            
            //log.log(Level.WARNING, "day passed in "+normDate);
            //log.log(Level.WARNING, "now "+today+", max "+tomorrow);
            
            Query query = pm.newQuery(Slide.class);
            query.setFilter("publishDate == publishDateParam");
            query.declareParameters("Long publishDateParam");
            List<Slide> results = (List<Slide>) query.execute(normDate);
            
            if ( results.size() > 0 )
            {
                slide = (Slide) results.get(0);
                setSlideInCache(slide);
                return(slide);
            }
            else
                return(null);
        }
        catch (Throwable t)
        {
            log.log(Level.WARNING, "Could not query for slide by day "+normDate, t);
            return(null);
        }
        finally
        {
            pm.close();
        }
    }
    
    public static Slide getMostRecentSlide(Long currentSlideDay)
    {
        PersistenceManager pm = PMF.get().getPersistenceManager();
        Slide slide = null;
        try
        {
            Query query = pm.newQuery(Slide.class);
            query.setFilter("publishDate <= publishDateParam");
            query.setRange(0, 1);
            query.setOrdering("publishDate desc");
            query.declareParameters("Long publishDateParam");
            List<Slide> results = (List<Slide>) query.execute(currentSlideDay);
            
            if ( results.size() > 0 )
            {
                slide = (Slide) results.get(0);
                
                //log.log(Level.WARNING, "##### slide a: "+currentSlideDay);
                //log.log(Level.WARNING, "##### slide: "+slide.getPublishDate());
                
                setSlideInCache(slide);
                return(slide);
            }
            else
                return(null);
        }
        catch (Throwable t)
        {
            log.log(Level.WARNING, "Could not query for slide by day "+currentSlideDay, t);
            return(null);
        }
        finally
        {
            pm.close();
        }
    }
    
    private static Slide getSlideFromCache(Long day)
    {
        if ( cache == null )
            return(null);
        
        if ( !cache.containsKey(day))
            return(null);           
        else
        {
            Slide slide = (Slide) cache.get(day);
            log.log(Level.WARNING, "found slide in cache for date "+day);
            log.log(Level.WARNING, "slide id "+slide.getExtension());
            if ( (slide != null) && (slide.getPublishDate() != null) )
                return(slide);
            else
                return(null);
        }
    }
    
    private static void setSlideInCache(Slide slide)
    {
        if ( cache == null )
            return;
        
        log.log(Level.WARNING, "putting slide in cache"+slide.getPublishDate()+", "+slide.getExtension());
        
        try 
        {
            cache.put(slide.getPublishDate(), slide);
        }
        catch (Throwable t) 
        {
            log.log(Level.WARNING, "Could put slide into the cache", t);
        }        
    }
    
    public static void removeSlideFromCache(Long pubDate)
    {
        if ( cache == null )
            return;
        
        try 
        {
            cache.remove(pubDate);
        } 
        catch (Throwable t) 
        {
            log.log(Level.WARNING, "Could remove slide from the cache", t);
        }        
    }
    
    public static void clearCache()
    {
        try
        {
            cache.clear();
        }
        catch (Throwable t) 
        {
            log.log(Level.WARNING, "Could clear cache", t);
        }   
    }
    
    public static void setMOTD(MOTD motd)
    {
        try 
        {
            cache.put("motd", motd);
        } 
        catch (Throwable t) 
        {
            log.log(Level.WARNING, "Could set MOTD in the cache", t);
        } 
    }
    
    public static MOTD getMOTD()
    {
        try 
        {
            MOTD motd = (MOTD) cache.get("motd");
            
            if ( motd == null )
            {
                Key key = KeyFactory.createKey(MOTD.class.getSimpleName(), "motd");
                PersistenceManager pm = PMF.get().getPersistenceManager();
                try
                {
                    motd = (MOTD) pm.getObjectById(MOTD.class, key);
                    setMOTD(motd);
                }
                catch (Throwable t)
                {
                    log.log(Level.WARNING, "No MOTD in data store, returning null");
                }
                finally
                {
                    pm.close();
                }
            }
            return(motd);
        } 
        catch (Throwable t) 
        {
            log.log(Level.WARNING, "No MOTD in data store, returning null");
            return(null);
        } 
    }
}
